package com.speljohan.rsbot.gui;

import java.awt.Component;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.LayoutStyle;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;

import com.speljohan.rsbot.account.Account;
import com.speljohan.rsbot.account.AccountHandler;

public class AccountManager extends JDialog implements ListSelectionListener,
ActionListener {
	private static class AccountModel extends AbstractListModel {
		private static final long serialVersionUID = 5930519463907602952L;
		public List<Account> accounts = new ArrayList<Account>();
		private final JList accountSelector;

		public AccountModel(final JList accountSelector) {
			this.accountSelector = accountSelector;
		}

		public int addAccount(final Account a) {
			int idx = Collections.binarySearch(accounts, a);
			if (idx >= 0) {
				accounts.set(idx, a);
				fireContentsChanged(this, idx, idx);
			} else {
				idx = -idx - 1;
				accounts.add(idx, a);
				fireIntervalAdded(this, idx, idx);
			}
			return idx;
		}

		public void addAllcopy(final Set<Account> accountList) {
			if (accounts.size() == 0) { // short cut
				for (final Account a : accountList) {
					accounts.add(new Account(a.getAllProperties()));
				}
				fireIntervalAdded(this, 0, getSize());
				sort();
			} else {
				for (final Account a : accountList) {
					addAccount(new Account(a.getAllProperties()));
				}
			}
		}

		public Object getElementAt(final int index) {
			return accounts.get(index);
		}

		public int getSize() {
			return accounts.size();
		}

		public void removeAccount(final int idx) {
			accounts.remove(idx);
			fireIntervalRemoved(this, idx, idx);
		}

		public void sort() {
			final Account a = accountSelector.getSelectedIndex() != -1 ? accounts
					.get(accountSelector.getSelectedIndex())
					: null;
					Collections.sort(accounts);
					fireContentsChanged(this, 0, getSize());
					if (a != null) {
						accountSelector.setSelectedIndex(accounts.indexOf(a));
					}
		}
	}

	private static class PropertiesModel extends AbstractTableModel {
		private static final String[] columnNames = new String[] { "Key",
		"Value" };
		private static final long serialVersionUID = -4633481224554091045L;
		public Account account;
		public List<String> keys;

		public PropertiesModel(final Account account) {
			this.account = account;
			if (account != null) {
				keys = new ArrayList<String>(account.getAllProperties()
						.keySet());
				sortKeys();
			}
		}

		public void addKey(final String key) {
			if (account == null) {
				throw new RuntimeException("EmptyModel");
			}
			if (keys.contains(key)) {
				return;
			}
			account.addProperty(key, "");
			keys.add(key);
			sortKeys();
		}

		@Override
		public Class<?> getColumnClass(final int columnIndex) {
			return String.class;
		}

		public int getColumnCount() {
			return PropertiesModel.columnNames.length;
		}

		@Override
		public String getColumnName(final int column) {
			return PropertiesModel.columnNames[column];
		}

		public int getRowCount() {
			if (account == null) {
				return 4;
			}
			return keys.size();
		}

		public Object getValueAt(final int rowIndex, final int columnIndex) {
			if (account == null) {
				return "";
			}
			final String key = keys.get(rowIndex);
			if (columnIndex == 0) {
				return key;
			} else {
				return account.getProperty(key);
			}
		}

		@Override
		public boolean isCellEditable(final int rowIndex, final int columnIndex) {
			return account != null && columnIndex == 1;
		}

		@Override
		public void setValueAt(final Object value, final int rowIndex,
				final int columnIndex) {
			if (columnIndex != 1) {
				throw new RuntimeException("what the...");
			}
			final String key = keys.get(rowIndex);
			account.addProperty(key, value.toString());
			fireTableCellUpdated(rowIndex, 1);
		}

		public void sortKeys() {
			Collections.sort(keys);
			keys.remove("USERNAME");
			keys.remove("PASSWORD");
			fireTableDataChanged();
		}
	}

	private static final long serialVersionUID = -1963288568987692910L;
	private final AccountHandler accountHandler;
	private AccountModel accountModel;
	private JList accountSelector;

	private final List<Component> accountSpecificList = new ArrayList<Component>();

	private PropertiesModel curModel;
	private PropertiesModel emptyModel;
	private JTextField keyText;
	private JPasswordField passwordText;

	private JTable propertyTable;

	private JTextField usernameText;

	public AccountManager(final Frame f, final AccountHandler accountHandler) {
		super(f, "Account Manager", true);
		this.accountHandler = accountHandler;
		initComponents();
		loadAccounts();
	}

	public void actionPerformed(final ActionEvent e) {
		final String command = e.getActionCommand().intern(); // intern is
		// IMPORTANT
		if (command == "Add") {
			final Account a = new Account("EMPTY", "NOTSET");
			accountSelector.setSelectedIndex(accountModel.addAccount(a));
		} else if (command == "Remove") {
			final int idx = accountSelector.getSelectedIndex();
			if (idx != -1) {
				accountModel.removeAccount(idx);
			}
		} else if (command == "Add Key") {
			final String text = keyText.getText();
			if (text != null && !text.equals("")) {
				if (!Account.isValidKey(text)) {
					JOptionPane.showConfirmDialog(this, "Invalid key [" + text
							+ "]\n"
							+ "Keys have to be alphanumeric. (a-zA-Z0-9)",
							"Error: Account Manager",
							JOptionPane.OK_CANCEL_OPTION,
							JOptionPane.ERROR_MESSAGE);
				} else {
					curModel.addKey(text.toUpperCase());
				}
			}
		} else if (command == "Okay") {
			applyChanges();
			dispose();
		} else if (command == "Cancel") {
			dispose();
		} else if (command == "Apply") {
			applyChanges();
		}
	}

	public void applyChanges() {
		accountHandler.clearAccounts();
		for (final Account a : accountModel.accounts) {
			accountHandler.addAccount(a);
		}
		accountHandler.syncAccounts();
	}

	private void initComponents() {
		final JButton okayButton = new JButton("Okay");
		final JButton cancelButton = new JButton("Cancel");
		final JButton applyButton = new JButton("Apply");
		final JButton newAccountButton = new JButton("Add");
		final JButton removeButton = new JButton("Remove");
		final JLabel usernameLabel = new JLabel("Username:");
		final JLabel passwordLabel = new JLabel("Password:");
		final JButton addKeyButton = new JButton("Add Key");
		final JPanel accountPanel = new JPanel();
		final JScrollPane accountScroller = new JScrollPane();
		final JScrollPane propertyScroller = new JScrollPane();

		accountSelector = new JList();
		usernameText = new JTextField();
		passwordText = new JPasswordField( );
		propertyTable = new JTable();
		keyText = new JTextField();

		//
		okayButton.addActionListener(this);
		cancelButton.addActionListener(this);
		applyButton.addActionListener(this);
		newAccountButton.addActionListener(this);
		removeButton.addActionListener(this);
		addKeyButton.addActionListener(this);
		accountSelector.getSelectionModel().addListSelectionListener(this);
		usernameText.getDocument().addDocumentListener(new DocumentListener() {
			public void changedUpdate(final DocumentEvent e) {
				String username = usernameText.getText();
				if (username.equals("")) {
					username = "EMPTY";
				}
				final int idx = accountSelector.getSelectedIndex();
				if (idx != -1) {
					accountModel.accounts.get(idx).setUsername(username);
				}
			}

			public void insertUpdate(final DocumentEvent e) {
				changedUpdate(null);
			}

			public void removeUpdate(final DocumentEvent e) {
				changedUpdate(null);
			}
		});
		passwordText.getDocument().addDocumentListener(new DocumentListener() {
			public void changedUpdate(final DocumentEvent e) {
				// Fixed deprecation issue ~ Aelin
				String pass = String.valueOf( passwordText.getPassword( ) );
				if (pass.equals("")) {
					pass = "EMPTY";
				}
				final int idx = accountSelector.getSelectedIndex();
				if (idx != -1) {
					accountModel.accounts.get(idx).setPassword(pass);
				}
			}

			public void insertUpdate(final DocumentEvent e) {
				changedUpdate(null);
			}

			public void removeUpdate(final DocumentEvent e) {
				changedUpdate(null);
			}
		});
		usernameText.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(final FocusEvent e) {
				accountModel.sort(); // So the user name is in the right
				// place in the list
			}
		});
		accountSpecificList.add(addKeyButton);
		accountSpecificList.add(usernameText);
		accountSpecificList.add(passwordText);
		accountSpecificList.add(propertyTable);
		accountSpecificList.add(keyText);
		for (final Component c : accountSpecificList) {
			c.setEnabled(false);
		}

		setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

		accountModel = new AccountModel(accountSelector);
		accountSelector.setModel(accountModel);
		accountScroller.setViewportView(accountSelector);

		accountPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory
				.createEtchedBorder(), "Selected Account"));

		curModel = emptyModel = new PropertiesModel(null);
		propertyTable.setModel(curModel);
		propertyScroller.setViewportView(propertyTable);

		final GroupLayout accountPanelLayout = new GroupLayout(accountPanel);
		accountPanel.setLayout(accountPanelLayout);
		accountPanelLayout
		.setHorizontalGroup(accountPanelLayout
				.createParallelGroup(GroupLayout.Alignment.LEADING)
				.addGroup(
						accountPanelLayout
						.createSequentialGroup()
						.addContainerGap()
						.addGroup(
								accountPanelLayout
								.createParallelGroup(
										GroupLayout.Alignment.TRAILING)
										.addComponent(
												usernameLabel)
												.addComponent(
														addKeyButton)
														.addComponent(
																passwordLabel))
																.addPreferredGap(
																		LayoutStyle.ComponentPlacement.RELATED)
																		.addGroup(
																				accountPanelLayout
																				.createParallelGroup(
																						javax.swing.GroupLayout.Alignment.LEADING)
																						.addComponent(
																								usernameText,
																								javax.swing.GroupLayout.DEFAULT_SIZE,
																								111, 111)
																								.addComponent(
																										passwordText,
																										javax.swing.GroupLayout.DEFAULT_SIZE,
																										111, 111)
																										.addComponent(
																												keyText,
																												javax.swing.GroupLayout.DEFAULT_SIZE,
																												111, 111))
																												.addPreferredGap(
																														LayoutStyle.ComponentPlacement.RELATED)
																														.addComponent(propertyScroller,
																																GroupLayout.DEFAULT_SIZE, 213,
																																Short.MAX_VALUE)
																																.addContainerGap()));
		accountPanelLayout
		.setVerticalGroup(accountPanelLayout
				.createParallelGroup(GroupLayout.Alignment.LEADING)
				.addGroup(
						accountPanelLayout
						.createSequentialGroup()
						.addGroup(
								accountPanelLayout
								.createParallelGroup(
										GroupLayout.Alignment.LEADING)
										.addGroup(
												accountPanelLayout
												.createSequentialGroup()
												.addGroup(
														accountPanelLayout
														.createParallelGroup(
																GroupLayout.Alignment.BASELINE)
																.addComponent(
																		usernameLabel)
																		.addComponent(
																				usernameText,
																				GroupLayout.PREFERRED_SIZE,
																				GroupLayout.DEFAULT_SIZE,
																				GroupLayout.PREFERRED_SIZE))
																				.addPreferredGap(
																						LayoutStyle.ComponentPlacement.RELATED)
																						.addGroup(
																								accountPanelLayout
																								.createParallelGroup(
																										GroupLayout.Alignment.BASELINE)
																										.addComponent(
																												passwordLabel)
																												.addComponent(
																														passwordText,
																														GroupLayout.PREFERRED_SIZE,
																														GroupLayout.DEFAULT_SIZE,
																														GroupLayout.PREFERRED_SIZE))
																														.addPreferredGap(
																																LayoutStyle.ComponentPlacement.RELATED,
																																30,
																																Short.MAX_VALUE)
																																.addGroup(
																																		accountPanelLayout
																																		.createParallelGroup(
																																				GroupLayout.Alignment.BASELINE)
																																				.addComponent(
																																						addKeyButton)
																																						.addComponent(
																																								keyText,
																																								GroupLayout.PREFERRED_SIZE,
																																								GroupLayout.DEFAULT_SIZE,
																																								GroupLayout.PREFERRED_SIZE)))
																																								.addComponent(
																																										propertyScroller,
																																										GroupLayout.DEFAULT_SIZE,
																																										125,
																																										Short.MAX_VALUE))
																																										.addContainerGap()));

		final GroupLayout layout = new GroupLayout(getContentPane());
		getContentPane().setLayout(layout);
		layout
		.setHorizontalGroup(layout
				.createParallelGroup(GroupLayout.Alignment.LEADING)
				.addGroup(
						layout
						.createSequentialGroup()
						.addContainerGap()
						.addComponent(newAccountButton)
						.addPreferredGap(
								LayoutStyle.ComponentPlacement.RELATED)
								.addComponent(removeButton))
								.addGroup(
										GroupLayout.Alignment.TRAILING,
										layout
										.createSequentialGroup()
										.addContainerGap()
										.addGroup(
												layout
												.createParallelGroup(
														GroupLayout.Alignment.TRAILING)
														.addComponent(
																accountScroller,
																GroupLayout.Alignment.LEADING,
																GroupLayout.DEFAULT_SIZE,
																464,
																Short.MAX_VALUE)
																.addComponent(
																		accountPanel,
																		GroupLayout.Alignment.LEADING,
																		GroupLayout.DEFAULT_SIZE,
																		GroupLayout.DEFAULT_SIZE,
																		Short.MAX_VALUE)
																		.addGroup(
																				layout
																				.createSequentialGroup()
																				.addComponent(
																						applyButton)
																						.addPreferredGap(
																								LayoutStyle.ComponentPlacement.RELATED)
																								.addComponent(
																										okayButton)
																										.addPreferredGap(
																												LayoutStyle.ComponentPlacement.RELATED)
																												.addComponent(
																														cancelButton)))
																														.addContainerGap()));
		layout.setVerticalGroup(layout.createParallelGroup(
				GroupLayout.Alignment.LEADING)
				.addGroup(
						layout.createSequentialGroup().addContainerGap()
						.addComponent(accountScroller,
								GroupLayout.PREFERRED_SIZE, 98,
								GroupLayout.PREFERRED_SIZE)
								.addPreferredGap(
										LayoutStyle.ComponentPlacement.RELATED)
										.addComponent(accountPanel,
												GroupLayout.DEFAULT_SIZE,
												GroupLayout.DEFAULT_SIZE,
												Short.MAX_VALUE).addPreferredGap(
														LayoutStyle.ComponentPlacement.RELATED)
														.addGroup(
																layout.createParallelGroup(
																		GroupLayout.Alignment.BASELINE)
																		.addComponent(cancelButton)
																		.addComponent(okayButton)
																		.addComponent(newAccountButton)
																		.addComponent(applyButton)
																		.addComponent(removeButton))
																		.addContainerGap()));
		pack();
		setLocationRelativeTo(getOwner());
	}

	/**
	 * Loads the accounts into the model.
	 */
	public void loadAccounts() {
		accountModel.accounts.clear();
		accountHandler.syncAccounts();
		accountModel.addAllcopy(accountHandler.getAccounts());
	}

	public void valueChanged(final ListSelectionEvent e) {
		final int idx = accountSelector.getSelectedIndex();
		if (idx != -1) {
			final Account selectedAccount = accountModel.accounts.get(idx);
			if (selectedAccount != null) {
				usernameText.setText(selectedAccount.getUsername());
				passwordText.setText(selectedAccount.getPassword());
				propertyTable.setModel(curModel = new PropertiesModel(
						selectedAccount));
				for (final Component c : accountSpecificList) {
					c.setEnabled(true);
				}
				return;
			}
		}
		usernameText.setText("");
		passwordText.setText("");
		propertyTable.setModel(emptyModel);
		for (final Component c : accountSpecificList) {
			c.setEnabled(false);
		}
	}
}
